package com.shanlitech.bluetooth.calibration;

import android.content.Context;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.shanlitech.bluetooth.AppContext;
import com.shanlitech.bluetooth.BluetoothScanner;
import com.shanlitech.bluetooth.MapBuilder;
import com.shanlitech.lbs.geometry.Point2D;
import com.shanlitech.lbs.geometry.Point3D;
import com.shanlitech.lbs.map.BleBeacon;
import com.shanlitech.lbs.map.CalibrationPoint;
import com.shanlitech.lbs.map.LbsDatabase;
import com.shanlitech.lbs.map.MapFlat;
import com.shanlitech.lbs.sample.CalibrationSample;
import com.shanlitech.lbs.types.RssiRecord;
import com.shanlitech.lbs.calibration.*;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

public class CalibrationScanner {
    private static final String TAG = "SLBluetooth";

    private static CalibrationScanner instance;

    public static CalibrationScanner getInstance(Context context) {
        if( instance == null ) {
            instance = new CalibrationScanner(context);
        }
        return instance;
    }

    public interface Listener {
        void onUpdate(float complete, HashMap<BleBeacon,Float> beaconComplete);
    }

    private final Context mContext;
    private final BluetoothScanner mScanner;
    private boolean mStart = false;
    private int mMinSampleCount = 20;
    private Listener mListener = null;
    private MapFlat mFlat = null;
    private final Point3D mLocation = new Point3D();
    private final HashMap<String,BleBeacon> mBeacons = new HashMap<>();
    private final HashMap<String,List<RssiRecord>> mBeaconRecords = new HashMap<>();
    private final LinkedList<RssiRecord> mRecords = new LinkedList<>();

    public CalibrationScanner(Context context) {
        mContext = context;
        mScanner = new BluetoothScanner(context);

    }

    public HashSet<BleBeacon> getBeacons() {
        return new HashSet<BleBeacon>(mBeacons.values());
    }

    private void reset() {
        mListener = null;
        mFlat = null;
        mBeacons.clear();
        mBeaconRecords.clear();
        mRecords.clear();
    }

    public boolean start(MapFlat flat, Point2D location,double distance, int minSampleCount,Listener listener) {
        if( !mStart ) {
            reset();
            final HashSet<BleBeacon> beacons = flat.getBeaconSetInRange(location,distance);
            if( beacons != null && ! beacons.isEmpty() ) {
                for(BleBeacon beacon : beacons) {
                    mBeacons.put(beacon.uuid,beacon);
                    mBeaconRecords.put(beacon.uuid,new ArrayList<>());
                }
                if (mScanner.start(this::onScan, mBeacons.keySet(), 1000)) {
                    mStart = true;
                    mFlat = flat;
                    mLocation.copy(location);
                    mMinSampleCount = minSampleCount;
                    mListener = listener;
                }
            }
        }
        return mStart;
    }

    public void stop() {
        if( !mStart ) return;
        mStart = false;
        mScanner.stop();
        reset();
    }

    public boolean isStart() {
        return mStart;
    }

    private void onScan(List<RssiRecord> records) {
        for(RssiRecord r : records) {
            final List<RssiRecord> rl = mBeaconRecords.get(r.uuid);
            if( rl != null ) {
                rl.add(r);
                mRecords.add(r);
            }
        }
        final HashMap<BleBeacon,Float> complete = new HashMap<>();
        int finishCount = 0;
        for(BleBeacon beacon : mBeacons.values()) {
            final List<RssiRecord> rl = mBeaconRecords.get(beacon.uuid);
            assert rl != null;
            complete.put(beacon,rl.size() / (float)mMinSampleCount);
            if( rl.size() >= mMinSampleCount ) {
                finishCount += 1;
            }
        }
        mListener.onUpdate(finishCount / (float)mBeacons.size(),complete);
    }

    public CalibrationSample build() {
        if( mBeaconRecords.isEmpty() ) {
            return null;
        }
        final MapFlat flat = AppContext.getFlat();
        return new CalibrationSample(flat.getPartition(),flat.getFloor(),Calendar.getInstance().getTime(),mLocation,mRecords);
    }


    /*
    public void saveRecords() {
        if( mScanData.isEmpty() ) { return; }

        final JSONArray json = new JSONArray();
        for(ScanData data : mScanData) {
            json.add(data.record.toJsonObject());
        }
        final String jsonStr = json.toString(SerializerFeature.PrettyFormat);

        File file = new File(mContext.getFilesDir(), "scan.json");
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
            writer.write(jsonStr);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void saveToMap() {
        if( mScanData.isEmpty() ) return;
        for(ScanData data : mScanData) {
            final CalibrationPoint cp = new CalibrationPoint(UUID.randomUUID().toString(), data.record.location);
            for( com.shanlitech.lbs.calibration.BeaconCalibration analysis : data.analysis.analysis.values()) {
                cp.addBeaconData(analysis);
            }
            data.record.mFlat.addCalibrationPoint(cp);
        }
        clear();

        MapBuilder.writeFile(mContext);
    }
     */

    /*
    public static class BeaconModulusFix {
        public final BleBeacon beacon;
        public final double RSSI_A;
        public final double RSSI_N;

        public BeaconModulusFix(BleBeacon beacon,double A,double N) {
            this.beacon = beacon;
            this.RSSI_A = A;
            this.RSSI_N = N;
        }
    }

    public List<BeaconModulusFix> calc() {
        final List<BeaconModulusFix> fixes = new ArrayList<>();
        HashMap<BleBeacon,BeaconModulusCalculator> calculators = new HashMap<>();
        for(ScanRecord scanRecord : mBeaconRecords.getScanRecords()) {
            for(BeaconRecord bsr : scanRecord.records.values()) {
                BeaconModulusCalculator calc = calculators.get(bsr.beacon);
                if( calc == null ) {
                    calc = new BeaconModulusCalculator();
                    calculators.put(bsr.beacon,calc);
                }
                for(RssiSignal signal : bsr.signals) {
                    calc.add(Point3D.distance(scanRecord.location,bsr.beacon.location),signal.rssi);
                }
            }
        }
        for(BleBeacon beacon : calculators.keySet()) {
            BeaconModulusCalculator calc = calculators.get(beacon);
            assert calc != null;
            if( calc.calcWithA(59.0) ) {
                fixes.add(new BeaconModulusFix(beacon,calc.getA(),calc.getN()));
            } else {
                Log.d(TAG,"can NOT calc modulus for " + beacon.uuid);
            }
        }
        return fixes;
    }
     */

}
